﻿namespace LMT.Communication.Ipc
{
    using System.Collections.Generic;
    using System.IO;
    using System.IO.Pipes;
    using System.Threading;
    using System;

    /// <summary>
    /// 
    /// </summary>
    public class NamedPipeServer
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="clientId">The client id.</param>
        public delegate void ClientConnectHandler(int clientId);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="data">The data.</param>
        public delegate void ReceiveDataHandler(int clientId, byte[] data);

        /// <summary>
        /// Occurs when [client connected].
        /// </summary>
        public event ClientConnectHandler ClientConnected;

        /// <summary>
        /// Occurs when [client disconnect].
        /// </summary>
        public event ClientConnectHandler ClientDisconnect;

        /// <summary>
        /// Occurs when [receive data event].
        /// </summary>
        public event ReceiveDataHandler ReceiveDataEvent;

        /// <summary>
        /// 
        /// </summary>
        private Dictionary<int, PipeStream> clientList;

        /// <summary>
        /// 
        /// </summary>
        private string pipeName;

        /// <summary>
        /// 
        /// </summary>
        private int numberOfClient;

        /// <summary>
        /// 
        /// </summary>
        private bool isStarting;

        /// <summary>
        /// Initializes a new instance of the <see cref="TcpServer"/> class.
        /// </summary>
        /// <param name="pipeName">Name of the pipe.</param>
        /// <param name="numberClient">The number client.</param>
        public NamedPipeServer(string pipeName, int numberClient)
        {
            if (PipeHelper.Instance == null)
            {
                PipeHelper.CreateInstance(null);
            }

            this.pipeName = pipeName;
            this.numberOfClient = numberClient;
            this.clientList = new Dictionary<int, PipeStream>(numberClient);
        }

        /// <summary>
        /// Begins the listenning.
        /// </summary>
        public void Start()
        {
            Thread[] startUp = new Thread[4];
            for(int index = 0; index < this.numberOfClient; index++)
            {
                startUp[index] = new Thread(this.InternalThreadWaitingForClient);
                startUp[index].IsBackground = true;

                startUp[index].Start(index + 1);
            }

            this.isStarting = true;
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public void Stop()
        {
            this.isStarting = false;

            lock (this.clientList)
            {
                foreach(int key in this.clientList.Keys)
                {
                    this.clientList[key].Close();
                    this.clientList[key].Dispose();
                    this.clientList[key] = null;
                }
            }
        }

        /// <summary>
        /// Sends the data to all active clients.
        /// </summary>
        /// <param name="data">The data.</param>
        public void Send(byte[] data)
        {
            lock (this.clientList)
            {
                foreach (int clientId in this.clientList.Keys)
                {
                    if (this.clientList[clientId].IsConnected)
                    {
                        PipeHelper.Instance.SendData(this.clientList[clientId], data);
                    }
                }
            }
        }

        /// <summary>
        /// Sends the specified pipe id.
        /// </summary>
        /// <param name="pipeId">The pipe id.</param>
        /// <param name="message">The message.</param>
        public void Send(int pipeId, string message)
        {
            lock (this.clientList)
            {
                if (this.clientList.ContainsKey(pipeId) && this.clientList[pipeId].IsConnected)
                {
                    PipeHelper.Instance.SendData(this.clientList[pipeId], message);
                }
            }
        }

        /// <summary>
        /// Sends the data to specific client.
        /// </summary>
        /// <param name="pipeId">The pipe id.</param>
        /// <param name="data">The data.</param>
        public void Send(int pipeId, byte[] data)
        {
            lock (this.clientList)
            {
                if (this.clientList.ContainsKey(pipeId) && this.clientList[pipeId].IsConnected)
                {
                    PipeHelper.Instance.SendData(this.clientList[pipeId], data);        
                }
            }
        }

        /// <summary>
        /// Internals the thread waiting for client.
        /// </summary>
        /// <param name="data">The data.</param>
        private void InternalThreadWaitingForClient(object data)
        {
            try
            {
                NamedPipeServerStream pipeServer = new NamedPipeServerStream(this.pipeName, PipeDirection.InOut, this.numberOfClient, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
                pipeServer.WaitForConnection();

                if (pipeServer.IsConnected)
                {
                    int clientId = (int)data;

                    // if result = 0, it means reconnect
                    if (this.ClientConnected != null && this.AddClientToList(clientId, pipeServer) == 1)
                    {
                        this.ClientConnected(clientId);
                    }

                    this.ReceivingFromClient(clientId, pipeServer, new Queue<byte[]>(10));
                }
            }
            catch
            {
            }
        }

        /// <summary>
        /// Workings the with client.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="pipe">The pipe.</param>
        /// <param name="queueData">The receive data.</param>
        private void ReceivingFromClient(int clientId, PipeStream pipe, Queue<byte[]> queueData)
        {
            AutoResetEvent waitMsg = null;
            Thread processMsg = null;

            if (this.ReceiveDataEvent != null)
            {
                waitMsg = new AutoResetEvent(false);
                processMsg = new Thread(delegate() { this.ProcessClientMessage(clientId, pipe, queueData, waitMsg); });
                processMsg.Start();
            }

            try
            {
                byte[] data = null;
                while (this.isStarting && pipe.IsConnected)
                {
                    data = PipeHelper.Instance.ReadData(pipe);

                    if (this.ReceiveDataEvent != null)
                    {
                        queueData.Enqueue(data);
                        waitMsg.Set();
                    }

                    if (data == null)
                    {
                        if (!pipe.IsConnected)
                        {
                            break;
                        }
                    }
                }

                this.RemoveClientFromList(clientId);
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                {
                    //SocketException se = ex.InnerException as SocketException;
                    //if (se != null)
                    //{
                    //    if (se.ErrorCode == 10054)
                    //    {
                    //        this.RemoveClientFromList(tcpClient);
                    //    }
                    //}
                }
            }
            finally
            {
                if (waitMsg != null)
                {
                    waitMsg.Set();
                    processMsg.Join();
                    waitMsg.Close();
                    waitMsg = null;
                }

                if (queueData != null)
                {
                    queueData.Clear();
                    queueData = null;
                }

                if (this.ClientDisconnect != null)
                {
                    this.ClientDisconnect(clientId);
                }
            }
        }

        /// <summary>
        /// Processes the client message.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="pipe">The pipe.</param>
        /// <param name="receiveData">The receive data.</param>
        /// <param name="waitMsg">The wait MSG.</param>
        private void ProcessClientMessage(int clientId, PipeStream pipe, Queue<byte[]> receiveData, AutoResetEvent waitMsg)
        {
            do
            {
                if (receiveData.Count < 1)
                {
                    waitMsg.WaitOne();
                }

                if (pipe.IsConnected && this.ReceiveDataEvent != null)
                {
                    if (receiveData.Count > 0)
                    {
                        byte[] dataDeQueue = receiveData.Dequeue();
                        if (dataDeQueue != null)
                        {
                            this.ReceiveDataEvent(clientId, dataDeQueue);
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
            while (pipe.IsConnected);
        }

        /// <summary>
        /// Adds the client to list.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="pipe">The pipe.</param>
        /// <returns></returns>
        private int AddClientToList(int clientId, PipeStream pipe)
        {
            int addResult = 0;

            lock (this.clientList)
            {
                if (this.clientList.ContainsKey(clientId))
                {
                    this.clientList[clientId] = pipe;
                    addResult = 0;
                }
                else
                {
                    this.clientList.Add(clientId, pipe);
                    addResult = 1;
                }
            }

            return addResult;
        }

        /// <summary>
        /// Removes the client from list.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        private void RemoveClientFromList(int clientId)
        {
            lock (this.clientList)
            {
                if (this.clientList.ContainsKey(clientId))
                {
                    this.clientList[clientId].Close();
                    this.clientList[clientId].Dispose();
                    
                    this.clientList.Remove(clientId);
                }
            }
        }
    }
}